package grpc

import (
  "context"
  "crypto/tls"
  "crypto/x509"
  "fmt"
  grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
  grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
  grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
  grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
  grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials"
  "io/ioutil"
  "net"
  "os"
  "os/signal"
  v1 "strtv.cn/ad-service/pkg/api/v1"
  "strtv.cn/ad-service/pkg/logger"
  "strtv.cn/ad-service/pkg/protocol/grpc/middleware"
)

// RunServer runs gRPC service to publish Department service
func RunServer(ctx context.Context, v1API v1.DepartmentServiceServer, addr, port string) error {
	listen, err := net.Listen("tcp", addr+":"+port)
	if err != nil {
		return err
	}

  transportCredentials, err := GetServerCredentials()
  if err != nil {
		return err
	}

	// gRPC server startup options
	opts := []grpc.ServerOption{grpc.Creds(transportCredentials)}

	// Add unary interceptor
	opts = append(opts, grpc_middleware.WithUnaryServerChain(
		grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
		grpc_zap.UnaryServerInterceptor(middleware.ZapLogger(logger.Log), middleware.ZapOption()...),
		grpc_auth.UnaryServerInterceptor(middleware.AuthInterceptor),
		grpc_recovery.UnaryServerInterceptor(middleware.RecoveryInterceptor()),
	))

	// Add stream interceptor
	opts = append(opts, grpc_middleware.WithStreamServerChain(
		grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
		grpc_zap.StreamServerInterceptor(middleware.ZapLogger(logger.Log), middleware.ZapOption()...),
		grpc_auth.StreamServerInterceptor(middleware.AuthInterceptor),
		grpc_recovery.StreamServerInterceptor(middleware.RecoveryInterceptor()),
	))

  // register service
  server := grpc.NewServer(opts...)
  v1.RegisterDepartmentServiceServer(server, v1API)

  // graceful shutdown
  c := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt)
  go func() {
    for range c {
      // sig is a ^C, handle it
      logger.Log.Warn("shutting down gRPC server...")

      server.GracefulStop()

      <-ctx.Done()
    }
  }()

  // start gRPC server
  logger.Log.Info("starting gRPC server...")
  return server.Serve(listen)
}

func GetServerCredentials()(credentials.TransportCredentials, error) {
  cert, err := tls.LoadX509KeyPair("../../tls/server/server.pem", "../../tls/server/server.key")
  if err != nil {
    return nil, fmt.Errorf("tls.LoadX509KeyPair err: %v", err)
  }
  certPool := x509.NewCertPool()
  ca, err := ioutil.ReadFile("../../tls/ca.pem")
  if err != nil {
    return nil, fmt.Errorf("ioutil.ReadFile err: %v", err)
  }
  if ok := certPool.AppendCertsFromPEM(ca); !ok {
     return nil, fmt.Errorf("certPool.AppendCertsFromPEM err")
  }
  transportCredentials := credentials.NewTLS(&tls.Config{
    Certificates: []tls.Certificate{cert},
    ClientAuth:   tls.RequireAndVerifyClientCert,
    ClientCAs:    certPool,
  })
  return transportCredentials, nil
}
